// Copyright (c) 2023 Presto Labs Pte. Ltd.
// Author: leon, junxiao

#include <cmath>
#include <memory>
#include <string>
#include <utility>

#include "coin2/strategy/dex_strat/simple_amm_strategy.h"
#include "coin2/exchange/uniswap/api_util/nft_token_info.h"

namespace coin2::strategy::dex_strat {

namespace {

coin::proto::LatencyProto::LatencyTag GetFeedLatencyTag(const FeedUpdate& upd) {
  if (upd.is_book())
    return coin::proto::LatencyProto::BOOK_FEED_LATENCY;
  else if (upd.is_trade())
    return coin::proto::LatencyProto::TRADE_FEED_LATENCY;
  else
    return coin::proto::LatencyProto::FEED_LATENCY;
}

coin::proto::AccountRequestProto GetAccRequest(const MarketExchangeApi& mea) {
  coin::proto::AccountRequestProto acc_request;
  acc_request.set_market_type(MarketType_Name(mea.market));
  acc_request.set_exchange(ExchangeType_Name(mea.exchange));
  acc_request.set_api_version(mea.api);
  return acc_request;
}

std::string GetProductKey(const IProduct* product) {
  return fmt::format("{}.{}", product->absolute_norm(), product->exchange_name());
}

std::string GetProductKey(const OrderEvent* oe) {
  return fmt::format("{}.{}", oe->symbol(), ExchangeTypeToString(oe->exchange_type()));
}

bool HasMidPrice(const FeedUpdate& upd) {
  bool has_midp = false;
  if (upd.book().Bid0().has_value() &&
      upd.book().Ask0().has_value() &&
      upd.book().Bid0().value().price < upd.book().Ask0().value().price) {
    has_midp = true;
  }
  return has_midp;
}

std::optional<double> GetMidPrice(const FeedUpdate& upd) {
  if (HasMidPrice(upd)) {
    return (upd.book().Bid0().value().price + upd.book().Ask0().value().price) / 2;
  } else {
    return std::nullopt;
  }
}

void ConvertExecutorConfig(AggressiveExecutorConfig* executor_config) {
  if (executor_config->has_order_update_period_sec()) {
    executor_config->set_order_update_period(
        executor_config->order_update_period_sec() * 1e9);
  }
  if (executor_config->has_min_posting_period_sec()) {
    executor_config->set_min_posting_period(
        executor_config->min_posting_period_sec() * 1e9);
  }
  if (executor_config->has_max_posting_period_sec()) {
    executor_config->set_max_posting_period(
        executor_config->max_posting_period_sec() * 1e9);
  }
  if (executor_config->has_fill_cooldown_period_sec()) {
    executor_config->set_fill_cooldown_period(
        executor_config->fill_cooldown_period_sec() * 1e9);
  }
}

void InitPassiveExecutorConfig(
    const AggressiveExecutorConfig& agg_config,
    PassiveExecutorConfig* pass_config) {
  pass_config->set_mea(agg_config.mea());
  pass_config->set_symbol(agg_config.symbol());
  pass_config->set_min_pos(agg_config.min_pos());
  pass_config->set_max_pos(agg_config.max_pos());
  pass_config->set_reserve((agg_config.min_pos() + agg_config.max_pos())/2);
  pass_config->set_lot_size(agg_config.lot_size());
  pass_config->set_leverage(agg_config.leverage());
  pass_config->set_max_posting_period(agg_config.max_posting_period());
  pass_config->set_max_working_cnt(agg_config.max_working_cnt());
  pass_config->set_order_update_period(agg_config.order_update_period());
  pass_config->set_fill_cooldown_period(agg_config.fill_cooldown_period());
}
}  // namespace

SimpleAmmPricer::SimpleAmmPricer(
    const IProduct* trade_product,
    const IProduct* main_ref_product,
    RefProductConvertDirection ref_convert_direction,
    const IProduct* convert_ref_product,
    const SimpleAmmParameterConfig& parameter_config)
    : default_amount_(10000.0) {
  trade_product_key_ = GetProductKey(trade_product);
  main_ref_key_ = GetProductKey(main_ref_product);
  if (ref_convert_direction != RefProductConvertDirection::NONE && convert_ref_product != nullptr) {
    ref_product_convert_direction_ = ref_convert_direction;
    convert_ref_key_ = GetProductKey(convert_ref_product);
  } else {
    ref_product_convert_direction_ = RefProductConvertDirection::NONE;
  }
  ResetLpData();
  last_lp_out_percentage_ = 0.0;
  total_lp_loss_ = 0.0;
  total_fee_ = 0.0;
  total_net_profit_ = 0.0;

  uniswap_tick_cnt_ = 0;
  cur_trade_midp_ = 0.0;
  cur_trade_book_ts_ = 0;
  last_op_ts_ = 0;
  cur_lp_op_ = LiquidityOrder(LiquiditySide::UNKNOWN_LIQUIDITY_SIDE, 0.0, 0.0);

  CHECK(parameter_config.has_status_record_file_path());
  min_op_interval_ = parameter_config.min_op_interval();
  window_estimator_normal_record_cnt_ = parameter_config.window_estimator_normal_record_cnt();
  window_estimator_mid_record_cnt_ = parameter_config.window_estimator_mid_record_cnt();
  window_estimator_range_multiplier_ = parameter_config.window_estimator_range_multiplier();
  window_size_multiplier_ = parameter_config.window_size_multiplier();

  provide_signal_bar_ = parameter_config.provide_signal_bar();
  normal_withdraw_bias_record_cnt_ = parameter_config.normal_withdraw_bias_record_cnt();
  mid_provide_bias_record_cnt_ = parameter_config.mid_provide_bias_record_cnt();
  long_provide_bias_record_cnt_ = parameter_config.long_provide_bias_record_cnt();
  
  min_half_win_size_ = parameter_config.min_half_win_size();
  can_adjust_bar_ = parameter_config.can_adjust_bar();
  accept_loss_factor_ = parameter_config.accept_loss_factor();
  minus_bias_bar_ = parameter_config.minus_bias_bar();
  plus_bias_bar_ = parameter_config.plus_bias_bar();
  bias_bar_decay_max_size_ = parameter_config.bias_bar_decay_max_size();
  avg_price_out_of_range_ = parameter_config.avg_price_out_of_range();
  minus_bias_adjust_bar_ = parameter_config.minus_bias_adjust_bar();
  plus_bias_adjust_bar_ = parameter_config.plus_bias_adjust_bar();

  status_record_file_path_ = parameter_config.status_record_file_path();

  lp_fee_per_unit_ = parameter_config.lp_fee_per_unit();
  lp_loss_adjust_factor_ = parameter_config.lp_loss_adjust_factor();

  normal_win_estimator_ = std::make_unique<WindowEstimator>(window_estimator_normal_record_cnt_, window_estimator_range_multiplier_);
  mid_win_estimator_ = std::make_unique<WindowEstimator>(window_estimator_mid_record_cnt_, window_estimator_range_multiplier_);
  normal_withdraw_bias_calculator_ = std::make_unique<BiasCalculator>(normal_withdraw_bias_record_cnt_, 1.44, provide_signal_bar_);
  mid_provide_bias_calculator_ = std::make_unique<BiasCalculator>(mid_provide_bias_record_cnt_, 6.25, provide_signal_bar_);
  long_provide_bias_calculator_ = std::make_unique<BiasCalculator>(long_provide_bias_record_cnt_, 6.25, provide_signal_bar_);
}

void SimpleAmmPricer::ResetLpData() {
  has_lp_position_ = false;
  cur_lp_enter_price_ = 0.0;
  cur_lp_enter_amount_ = 0.0;
  cur_lp_enter_base_amount_ = 0.0;
  cur_lp_enter_quote_amount_ = 0.0;
  cur_lp_price_lower_ = 0.0;
  cur_lp_price_upper_ = 0.0;
  cur_lp_fee_loss_multiplier_ = 0.0;
  cur_lp_in_cnt_ = 0;
  cur_lp_out_cnt_ = 0;
  cur_lp_loss_ = 0.0;
  cur_lp_fee_ = 0.0;
  cur_loss_per_unit_ = 0.0;
}

void SimpleAmmPricer::CalculateCurrentLoss(double cur_price) {
  if (!has_lp_position_ || cur_lp_enter_price_ < 0.0001) {
    return;
  }
  double lp_quit_base_amount = cur_lp_enter_base_amount_ * std::sqrt(cur_lp_enter_price_ / cur_price);
  double lp_quit_quote_amount = cur_lp_enter_quote_amount_ * std::sqrt(cur_price / cur_lp_enter_price_);
  double raw_loss = (cur_lp_enter_base_amount_ * cur_price + cur_lp_enter_quote_amount_) - (lp_quit_base_amount * cur_price + lp_quit_quote_amount);
  cur_lp_loss_ = raw_loss * lp_loss_adjust_factor_ * cur_lp_fee_loss_multiplier_;
}

void SimpleAmmPricer::CalculateCurrentFee(double cur_price) {
  if (!has_lp_position_ || cur_lp_enter_price_ < 0.0001) {
    return;
  }
  if (cur_price < cur_lp_price_upper_ && cur_price > cur_lp_price_lower_) {
    cur_lp_in_cnt_++;
    cur_lp_fee_ += lp_fee_per_unit_ * (cur_lp_enter_amount_ / default_amount_) * cur_lp_fee_loss_multiplier_;
  } else {
    cur_lp_out_cnt_++;
  }
}

double SimpleAmmPricer::CalculateCurrentAcceptWinSize(double cur_price) {
  if (!has_lp_position_) {
    return 0.0;
  }
  double accept_loss = cur_lp_fee_ * accept_loss_factor_;
  double accept_price_diff = std::sqrt(accept_loss / cur_loss_per_unit_);
  return accept_price_diff;
}

double SimpleAmmPricer::AdjustAcceptWinSize(double accept_win_size, double bias, double cur_price) {
  if (!has_lp_position_) {
    return accept_win_size;
  }

  double minus_factor = minus_bias_adjust_bar_ - 0.5;
  double plus_factor = plus_bias_adjust_bar_ - 0.5;
  if (bias > -(minus_bias_adjust_bar_ * minus_bias_adjust_bar_) && bias < (plus_bias_adjust_bar_ * plus_bias_adjust_bar_)) {
    return accept_win_size;
  }
  if ((cur_price - cur_lp_enter_price_) * bias < 0.0) {
    return accept_win_size;
  }

  double bias_factor = std::pow((std::sqrt(std::abs(bias)) - (bias < 0.0 ? minus_factor : plus_factor)) * 2.0, 2);
  accept_win_size *= bias_factor;
  return accept_win_size;
}

bool SimpleAmmPricer::BiasRealWithdraw(double bias, double cur_price) {
  double price_diff = cur_price - cur_lp_enter_price_;
  double base_minus_bias_bar = minus_bias_bar_;
  double base_plus_bias_bar = plus_bias_bar_;
  double half_win_size = bias_bar_decay_max_size_;
  double bias_bar_factor = 1.0;
  if (std::abs(price_diff) > can_adjust_bar_ + 0.001) {
    bias_bar_factor = (half_win_size - std::abs(price_diff)) / (half_win_size - can_adjust_bar_);
    bias_bar_factor = std::max(0.0, bias_bar_factor);
  }

  if (bias < 0.0) {
    double bias_bar = base_minus_bias_bar * bias_bar_factor;
    LOG(INFO) << fmt::format(
      "real bias bar:{}", bias_bar
    );
    return bias < -bias_bar;

  } else {
    double bias_bar = base_plus_bias_bar * bias_bar_factor;
    LOG(INFO) << fmt::format(
      "real bias bar:{}", bias_bar
    );
    return bias > bias_bar;
  }
}

bool SimpleAmmPricer::HasLpPosition() {
  return has_lp_position_;
}

void SimpleAmmPricer::AlterLpPositionExplicit(bool new_pos) {
  has_lp_position_ = new_pos;
}

LiquidityOrder SimpleAmmPricer::GetLiqidityOperation() {
  return cur_lp_op_;
}

LiquidityStatus SimpleAmmPricer::GetLiquidityStatus() {
  if (!has_lp_position_) {
    return LiquidityStatus();
  }

  return LiquidityStatus(cur_lp_enter_price_, cur_lp_price_upper_, cur_lp_price_lower_, cur_lp_enter_amount_, cur_lp_fee_);
}


bool SimpleAmmPricer::ReadyForProvide() {
  return normal_win_estimator_->HasEnoughRecord() && normal_withdraw_bias_calculator_->HasEnoughRecord()
    && mid_provide_bias_calculator_->HasEnoughRecord() && long_provide_bias_calculator_->HasEnoughRecord();
}

bool SimpleAmmPricer::ReadyForWithdraw() {
  return mid_win_estimator_->HasEnoughRecord() && normal_withdraw_bias_calculator_->HasEnoughRecord();
}

void SimpleAmmPricer::onBookFeed(const std::string& product_key, const FeedUpdate& upd) {
  if (product_key.length() == 0 || (product_key != trade_product_key_ && product_key != main_ref_key_ && product_key != convert_ref_key_)) {
    return;
  }
  if (!HasMidPrice(upd)) {
    LOG(INFO) << fmt::format(
        "Abnormal feed. product: {}", upd.product().absolute_norm());
    return;
  }

  if (product_key == main_ref_key_) {
    last_main_ref_midp_ = (upd.book().Ask0().value().price + upd.book().Bid0().value().price) / 2.0;
  } else if (product_key == convert_ref_key_) {
    last_convert_ref_midp_ = (upd.book().Ask0().value().price + upd.book().Bid0().value().price) / 2.0;
  } else if (product_key == trade_product_key_) {
    if (!last_main_ref_midp_) {
      return;
    }
    double ref_midp = last_main_ref_midp_.value();
    if (ref_product_convert_direction_ != RefProductConvertDirection::NONE) {
      if (!last_convert_ref_midp_) {
        return;
      }
      if (ref_product_convert_direction_ == RefProductConvertDirection::MULTIPLE) {
        ref_midp *= last_convert_ref_midp_.value();
      } else if (ref_product_convert_direction_ == RefProductConvertDirection::DIVIDE) {
        ref_midp /= last_convert_ref_midp_.value();
      }
    }
    double trade_midp = (upd.book().Ask0().value().price + upd.book().Bid0().value().price) / 2.0;
    LOG(INFO) << fmt::format(
        "trade product: {}, midp: {}, ref product: {}, midp: {}",
        trade_product_key_, trade_midp, main_ref_key_, ref_midp);

    uniswap_tick_cnt_++;
    cur_trade_midp_ = trade_midp;
    cur_trade_book_ts_ = upd.book().Timestamp();
    normal_win_estimator_->InsertRecord(trade_midp);
    mid_win_estimator_->InsertRecord(trade_midp);
    mid_provide_bias_calculator_->InsertRecord(trade_midp, ref_midp);
    long_provide_bias_calculator_->InsertRecord(trade_midp, ref_midp);
    normal_withdraw_bias_calculator_->InsertRecord(trade_midp, ref_midp);
    mid_provide_bias_calculator_->PrintInfo();
    long_provide_bias_calculator_->PrintInfo();
    normal_withdraw_bias_calculator_->PrintInfo();
    CalculateLiquidityOperation();
  }
}

void SimpleAmmPricer::onProvideSucceeded(double provide_price, double provide_price_upper, double provide_price_lower, double provide_amount, double start_fee) {
  if (has_lp_position_) {
    return;
  }

  has_lp_position_ = true;
  cur_lp_enter_price_ = provide_price;
  cur_lp_enter_amount_ = provide_amount;
  cur_lp_enter_base_amount_ = provide_amount / (provide_price * 2.0);
  cur_lp_enter_quote_amount_ = provide_amount / 2.0;
  cur_lp_price_lower_ = provide_price_lower;
  cur_lp_price_upper_ = provide_price_upper;
  cur_lp_fee_loss_multiplier_ = 100.0 / (provide_price_upper - provide_price_lower);
  cur_lp_in_cnt_ = 0;
  cur_lp_out_cnt_ = 0;
  cur_lp_loss_ = 0.0;
  cur_lp_fee_ = start_fee;
  CalculateCurrentLoss(provide_price + 1.0);
  cur_loss_per_unit_ = cur_lp_loss_;
  LOG(INFO) << fmt::format(
    "provide liquidity: enter price:{}, amount: {}, price lower:{}, price upper:{}, loss per unit:{}, start fee:{}",
    cur_lp_enter_price_, cur_lp_enter_amount_, cur_lp_price_lower_, cur_lp_price_upper_, cur_loss_per_unit_, cur_lp_fee_
  );
}

void SimpleAmmPricer::onWithdrawSucceeded(double withdraw_price) {
  if (!has_lp_position_) {
    return;
  }

  total_lp_loss_ += cur_lp_loss_;
  total_fee_ += cur_lp_fee_;
  total_net_profit_ = total_fee_ - total_lp_loss_;
  if ((cur_lp_out_cnt_ + cur_lp_in_cnt_) == 0) {
    last_lp_out_percentage_ = -1.0;
  } else {
    last_lp_out_percentage_ = static_cast<double>(cur_lp_out_cnt_) / static_cast<double>(cur_lp_in_cnt_ + cur_lp_out_cnt_);
  }
  LOG(INFO) << fmt::format(
    "withdraw liquidity: enter price:{}, quit price: {}, amount: {}, price lower:{}, price upper:{}, record span:{}, record out percentage:{}",
    cur_lp_enter_price_, withdraw_price, cur_lp_enter_amount_, cur_lp_price_lower_, cur_lp_price_upper_, cur_lp_in_cnt_ + cur_lp_out_cnt_, last_lp_out_percentage_
  );
  LOG(INFO) << fmt::format(
    "current status: current loss:{}, current fee: {}, total loss:{}, total fee:{}, total net profit:{}",
    cur_lp_loss_, cur_lp_fee_, total_lp_loss_, total_fee_, total_net_profit_
  );

  ResetLpData();
}

void SimpleAmmPricer::CalculateLiquidityOperation() {
  cur_lp_op_.SetVal(LiquiditySide::UNKNOWN_LIQUIDITY_SIDE, 0.0, 0.0);
  if (cur_trade_book_ts_ - last_op_ts_ < min_op_interval_) {
    return;
  }

  if (!mid_win_estimator_->HasEnoughRecord() || !normal_withdraw_bias_calculator_->HasEnoughRecord()) {
    return;
  }

  if (!has_lp_position_) {
    if (!normal_win_estimator_->HasEnoughRecord() 
      || !mid_provide_bias_calculator_->HasEnoughRecord() || !long_provide_bias_calculator_->HasEnoughRecord()) {
      return;
    }

    if (!mid_provide_bias_calculator_->ShouldProvide() || !long_provide_bias_calculator_->ShouldProvide()
      || !normal_withdraw_bias_calculator_->ShouldProvide()) {
      return;
    }

    double half_window_size = normal_win_estimator_->GetHalfWinSize() * window_size_multiplier_;
    if (half_window_size < 0.00001) {
      return;
    }

    half_window_size = std::max(half_window_size, min_half_win_size_);
    double price_upper = cur_trade_midp_ + half_window_size;
    double price_lower = std::max(cur_trade_midp_ - half_window_size, 0.001);
    LOG(INFO) << fmt::format(
        "provide signal: midp: {}, price_upper: {}, price_lower: {}",
        cur_trade_midp_, price_upper, price_lower);
    cur_lp_op_.SetVal(LiquiditySide::PROVIDE, price_upper, price_lower);
  } else {
    CalculateCurrentFee(cur_trade_midp_);
    CalculateCurrentLoss(cur_trade_midp_);
    double accept_price_diff = CalculateCurrentAcceptWinSize(mid_win_estimator_->GetAvgPrice());
    LOG(INFO) << fmt::format("accept price diff:{}", accept_price_diff);
    accept_price_diff = std::max(can_adjust_bar_, accept_price_diff);
    accept_price_diff = AdjustAcceptWinSize(accept_price_diff, normal_withdraw_bias_calculator_->GetAvgBiasVal(), cur_trade_midp_);
    LOG(INFO) << fmt::format("adjusted accept price diff:{}", accept_price_diff);
    bool need_withdraw = BiasRealWithdraw(normal_withdraw_bias_calculator_->GetAvgBiasVal(), cur_trade_midp_);

    if ((need_withdraw && std::abs(cur_trade_midp_ - cur_lp_enter_price_) < accept_price_diff)
      || normal_win_estimator_->GetAvgPrice() < cur_lp_price_lower_ - avg_price_out_of_range_
      || normal_win_estimator_->GetAvgPrice() > cur_lp_price_upper_ + avg_price_out_of_range_) {
      LOG(INFO) << fmt::format(
        "withdraw signal: midp: {}",
        cur_trade_midp_);
      cur_lp_op_.SetVal(LiquiditySide::WITHDRAW, 0.0, 0.0);
    }
  }
}

void SimpleAmmPricer::PrintLpInfo() {
  if (!has_lp_position_) {
    return;
  }

  LOG(INFO) << fmt::format(
    "current liquidity: enter price:{}, amount: {}, price lower:{}, price upper:{}, record in:{}, record out:{}",
    cur_lp_enter_price_, cur_lp_enter_amount_, cur_lp_price_lower_, cur_lp_price_upper_, cur_lp_in_cnt_, cur_lp_out_cnt_
  );
  LOG(INFO) << fmt::format(
    "current status: current loss:{}, current fee: {}, total loss:{}, total fee:{}, total net profit:{}",
    cur_lp_loss_, cur_lp_fee_, total_lp_loss_, total_fee_, total_net_profit_
  );
}


SimpleAmmStrategy::SimpleAmmStrategy(
    const std::experimental::filesystem::path& strat_json,
    bool dry_run,
    std::optional<int64_t> timestamp)
  : strategy_created_time_(timestamp.has_value() ? *timestamp : GetCurrentTimestamp()) {
  std::ifstream in(strat_json);
  LOG(INFO) << "Load strategy json: " << strat_json;
  CHECK_THROW(in.is_open());
  auto strat_config_str = ::nlohmann::json::parse(in).dump();
  SimpleAmmStrategyConfig strat_config;
  auto status = google::protobuf::util::JsonStringToMessage(
      strat_config_str, &strat_config);
  CHECK_THROW(status.ok()) << "Fail to parse DriverConfig from " << strat_config_str
                           << "\n" << status;
  for (auto& product_config : *strat_config.mutable_product_configs()) {
    product_config.mutable_agg_config()->set_symbol(product_config.trade_symbol());
    product_config.mutable_agg_config()->set_mea(product_config.trade_mea());
    ConvertExecutorConfig(product_config.mutable_agg_config());
  }
  LOG(INFO) << strat_config.DebugString();
  for (auto& product_config : strat_config.product_configs()) {
    simple_amm_sub_strats_.push_back(std::make_unique<SimpleAmmSubStrategy>(
        this,
        product_config,
        strategy_created_time_,
        strat_config.enable_telemetry(),
        dry_run));
  }
  for (auto& sub_strat : simple_amm_sub_strats_) {
    auto key = GetProductKey(sub_strat->GetTradeProduct());
    feed_sub_map_[key].push_back(sub_strat.get());
    CHECK(order_sub_map_.find(key) == order_sub_map_.end());
    order_sub_map_[key] = sub_strat.get();

    key = GetProductKey(sub_strat->GetMainRefProduct());
    feed_sub_map_[key].push_back(sub_strat.get());

    if ((sub_strat->GetConvertRefProduct()) != nullptr) {
      key = GetProductKey(sub_strat->GetConvertRefProduct());
      feed_sub_map_[key].push_back(sub_strat.get());
    }
  }
}

void SimpleAmmStrategy::InitAuthKey(::coin2::app::Driver* driver) {
  const auto& driver_config = driver->config();
  for (const auto& [exchange, order_config] : driver_config.order().exchanges()) {
    CHECK(!auth_key_);
    const auto& key_filepath = order_config.connection_config().key_filepath();
    auth_key_.reset(new AuthKey(AuthKey::FromFile(key_filepath)));
  }
}

void SimpleAmmStrategy::Init(::coin2::app::Driver* driver) {
  LOG(INFO) << "Simple Amm Strategy Init.";
  InitAuthKey(driver);
  strat_reporter_.reset(new StrategyReporter2(
      driver->strategy()->config()));
  for (auto& sub_strat : simple_amm_sub_strats_) {
    sub_strat->Init(driver, strat_reporter_.get());
    auto acc_request = GetAccRequest(sub_strat->mea());
    acc_request.set_owner(auth_key_->owner());
    auto curr_time = GetCurrentTimestamp();
    PassiveExecutorConfig pass_config;
    InitPassiveExecutorConfig(sub_strat->GetConfig().agg_config(), &pass_config);
    strat_reporter_->RegisterExecutorConfig(
        curr_time, acc_request,
        *sub_strat->GetTradeProduct(), pass_config);
  }
}

void SimpleAmmStrategy::InjectFeed(const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) {
  for (auto& elem : feed_sub_map_) {
    for (auto& sub_strat : elem.second) {
      sub_strat->InjectFeed(feed_mgr);
    }
  }
}

void SimpleAmmStrategy::onBookFeed(const FeedUpdate& upd) {
  const auto& key = GetProductKey(&upd.product());
  CHECK_GT(feed_sub_map_[key].size(), 0);
  for (auto& sub_strat : feed_sub_map_[key]) {
    sub_strat->onBookFeed(key, upd);
  }
  if (order_sub_map_.find(key) != order_sub_map_.end()) {
    auto midp = GetMidPrice(upd);
    if (midp.has_value()) {
      strat_reporter_->UpdateMidp(*order_sub_map_[key]->GetTradeProductForReporter(), midp);
    }
  }
}

void SimpleAmmStrategy::onAccountInfo(const OrderUpdate& upd) {
  for (auto& sub_strat : simple_amm_sub_strats_) {
    sub_strat->onAccountInfo(upd);
  }
  if (strat_reporter_) {
    strat_reporter_->onAccountInfo(upd);
  }
}

void SimpleAmmStrategy::onAccountOrder(const OrderUpdate& upd) {
  if (upd.product_order_info().event().type() == OrderEvent::ORDER_FILLED) {
    const auto& key = GetProductKey(&upd.product_order_info().event());
    if (order_sub_map_.count(key) == 0) {
      LOG(INFO) << fmt::format("Received unexpected fills {}", key);
    } else {
      order_sub_map_[key]->onAccountOrder(upd);
    }
  }
}

void SimpleAmmStrategy::onOrderLog(const coin::proto::OrderGatewayLog& order_log) {
  if (strat_reporter_) {
    strat_reporter_->GetMutableStrategyLogger()->WriteOrderLog(
        order_log,
        true,
        GetCurrentTimestamp());
  }
}

SimpleAmmSubStrategy::SimpleAmmSubStrategy(
    SimpleAmmStrategy* parent,
    const SimpleAmmProductConfig& product_config,
    int64_t strategy_created_time,
    bool enable_telemetry,
    bool dry_run)
  : parent_(parent),
    config_(product_config),
    strategy_created_time_(strategy_created_time),
    driver_(nullptr),
    strat_logger_(nullptr),
    enable_telemetry_(enable_telemetry),
    dry_run_(dry_run),
    trade_tick_cnt_(0), pos_initialized_(false), is_pos_updating_(false), is_returning_pos_(false),
    total_nft_token_cnt_(0),
    default_amount_(10000.0), last_op_ts_(0), op_provide_price_(0.0),
    op_provide_price_upper_(0.0), op_provide_price_lower_(0.0), op_provide_amount_(0.0),
    op_withdraw_price_(0.0) {
  CHECK(parent_);
  CHECK(config_.has_trade_symbol());
  CHECK(config_.has_trade_mea());
  CHECK(config_.has_agg_config());
  CHECK(config_.has_parameter_config());
  CHECK(config_.has_main_ref_config());
  trade_product_ = CreateProductFromNormString(
      MarketExchangeApi::FromString(config_.trade_mea()),
      config_.trade_symbol(),
      strategy_created_time);
  feed_keys_.insert(GetProductKey(GetTradeProduct()));
  std::string trade_symbol_name = config_.trade_symbol();
  std::string processed_symbol_name = trade_symbol_name;
  int64_t dot_index = trade_symbol_name.rfind(".");
  if (dot_index != std::string::npos) {
    processed_symbol_name = trade_symbol_name.substr(0, dot_index);
  }
  trade_product_for_reporter_ = CreateProductFromNormString(
      MarketExchangeApi::FromString(config_.trade_mea()),
      processed_symbol_name,
      strategy_created_time);

  main_ref_product_ = CreateProductFromNormString(
      MarketExchangeApi::FromString(config_.main_ref_config().mea()),
      config_.main_ref_config().symbol(),
      strategy_created_time);
  ref_feed_keys_.insert(GetProductKey(main_ref_product_.get()));

  if (config_.ref_convert_direction() != RefProductConvertDirection::NONE && config_.has_convert_ref_config()) {
    ref_product_convert_direction_ = config_.ref_convert_direction();
    convert_ref_product_ = CreateProductFromNormString(
      MarketExchangeApi::FromString(config_.convert_ref_config().mea()),
      config_.convert_ref_config().symbol(),
      strategy_created_time);
    ref_feed_keys_.insert(GetProductKey(convert_ref_product_.get()));
  } else {
    ref_product_convert_direction_ = RefProductConvertDirection::NONE;
  }
  auto agg_config = config_.agg_config();
  auto parameter_config = config_.parameter_config();
  reserved_pos_ = agg_config.maintain_open_pos();
  lot_size_ = agg_config.lot_size();
  max_pos_ = agg_config.max_pos();
  min_pos_ = agg_config.min_pos();
  current_pos_ = reserved_pos_;

  CHECK(parameter_config.has_status_record_file_path());
  min_op_interval_ = parameter_config.min_op_interval();
  window_estimator_normal_record_cnt_ = parameter_config.window_estimator_normal_record_cnt();
  window_estimator_mid_record_cnt_ = parameter_config.window_estimator_mid_record_cnt();
  window_estimator_range_multiplier_ = parameter_config.window_estimator_range_multiplier();
  window_size_multiplier_ = parameter_config.window_size_multiplier();

  provide_signal_bar_ = parameter_config.provide_signal_bar();
  normal_withdraw_bias_record_cnt_ = parameter_config.normal_withdraw_bias_record_cnt();
  mid_provide_bias_record_cnt_ = parameter_config.mid_provide_bias_record_cnt();
  long_provide_bias_record_cnt_ = parameter_config.long_provide_bias_record_cnt();
  
  min_half_win_size_ = parameter_config.min_half_win_size();
  can_adjust_bar_ = parameter_config.can_adjust_bar();
  accept_loss_factor_ = parameter_config.accept_loss_factor();
  minus_bias_bar_ = parameter_config.minus_bias_bar();
  plus_bias_bar_ = parameter_config.plus_bias_bar();
  bias_bar_decay_max_size_ = parameter_config.bias_bar_decay_max_size();
  avg_price_out_of_range_ = parameter_config.avg_price_out_of_range();
  minus_bias_adjust_bar_ = parameter_config.minus_bias_adjust_bar();
  plus_bias_adjust_bar_ = parameter_config.plus_bias_adjust_bar();

  return_pos_after_withdraw_ = parameter_config.return_pos_after_withdraw();
  return_pos_diff_ratio_ = parameter_config.return_pos_diff_ratio();
  return_pos_valid_ratio_ = parameter_config.return_pos_valid_ratio();

  status_record_file_path_ = parameter_config.status_record_file_path();

  lp_fee_per_unit_ = parameter_config.lp_fee_per_unit();
  lp_loss_adjust_factor_ = parameter_config.lp_loss_adjust_factor();

  pricer_ = std::make_unique<SimpleAmmPricer>(
    trade_product_.get(), main_ref_product_.get(), ref_product_convert_direction_, convert_ref_product_.get(),
    parameter_config);
}

void SimpleAmmSubStrategy::Init(
    ::coin2::app::Driver* driver,
    ::coin2::strategy::util::StrategyReporter2* reporter) {
  driver_ = driver;
  strat_logger_ = reporter->GetMutableStrategyLogger();
  auto get_product_holder = [this](const coin2::exchange::base::symbology::IProduct& product, int64_t timestamp) {
      return driver_->product_holder_manager()->GetProductHolder(product, timestamp);
  };
  executor_ = std::make_unique<AggOrderExecutor>(
      config_.agg_config(), os(), get_product_holder);
  if (enable_telemetry_) {
    LOG(INFO) << "Set Latency Recorder";
    os()->gateway()->set_latency_recorder(
         strat_logger_->mutable_latency_recorder());
  }
}

void SimpleAmmSubStrategy::InjectFeed(
    const coin2::feed::mgr::impl::FeedSubsystemManager* feed_mgr) {
  feed_mgr_ = feed_mgr;
}

void SimpleAmmSubStrategy::onAccountInfo(const OrderUpdate& upd) {
  if (!upd.is_account_info()) {
    return;
  }

  auto nft_token_info = upd.account_info().GetNftTokenInfo();
  int64_t total_nft_token_cnt = 0;
  for (const auto& each_nft_token_info : nft_token_info.each_nft_token_info()) {
    total_nft_token_cnt += each_nft_token_info.tokenid_to_nft_info_size();
  }
  total_nft_token_cnt_ = total_nft_token_cnt;
}

void SimpleAmmSubStrategy::WriteStatusToFile(const LiquidityStatus& liquidity_status) {
  try {
    std::ofstream status_file(status_record_file_path_);
    if (status_file.is_open()) {
      if (liquidity_status.cur_price && liquidity_status.price_upper && liquidity_status.price_lower && liquidity_status.amount && liquidity_status.cur_fee) {
        status_file << liquidity_status.cur_price.value() << "," << liquidity_status.price_upper.value() << "," << liquidity_status.price_lower.value()
          << "," << liquidity_status.amount.value() << "," << liquidity_status.cur_fee.value();
      }
    } else {
      LOG(INFO) << "Cannot open status file during writing status:" << status_record_file_path_;
    }
  } catch (std::exception& e) {
      LOG(ERROR) << "Exception during writing status file:" << e.what();
  }
}

LiquidityStatus SimpleAmmSubStrategy::ReadStatusFromFile(double default_price) {
  LiquidityStatus liquidity_status(default_price, default_price + min_half_win_size_, std::max(0.001, default_price - min_half_win_size_), default_amount_, 0.0);
  try {
    std::ifstream status_file(status_record_file_path_);
    if (status_file.is_open()) {
      std::string line;
      std::getline(status_file, line);
      double cur_price, price_upper, price_lower, amount, cur_fee;
      int64_t filled_cnt = sscanf(line.c_str(), "%lf,%lf,%lf,%lf,%lf", &cur_price, &price_upper, &price_lower, &amount, &cur_fee);
      if (filled_cnt == 5) {
        liquidity_status.cur_price = cur_price;
        liquidity_status.price_upper = price_upper;
        liquidity_status.price_lower = price_lower;
        liquidity_status.amount = amount;
        liquidity_status.cur_fee = cur_fee;
      } else {
        LOG(INFO) << "Some fields are missing in status file:" << status_record_file_path_;
      }
    } else {
      LOG(INFO) << "Cannot open status file during reading status:" << status_record_file_path_;
    }
  } catch (std::exception& e) {
      LOG(ERROR) << "Exception during reading status file:" << e.what();
  }
  return liquidity_status;
}

void SimpleAmmSubStrategy::ReturnPosAfterWithdraw(double price) {
  double cur_pos = executor_->GetPosition();
  if (cur_pos < return_pos_valid_ratio_ * reserved_pos_) {
    std::this_thread::sleep_for(std::chrono::milliseconds(5000));
    cur_pos = executor_->GetPosition();
  }
  LOG(INFO) << fmt::format(
    "Return pos after withdraw. cur_pos:{}, reserve_pos:{}",
    cur_pos, reserved_pos_
  );
  if (cur_pos < return_pos_valid_ratio_ * reserved_pos_) {
    return;
  }
  double pos_diff = reserved_pos_ - cur_pos;
  if (std::abs(pos_diff) > return_pos_diff_ratio_ * reserved_pos_ && !is_returning_pos_) {
    is_returning_pos_ = true;
    LOG(INFO) << fmt::format(
      "Return pos after withdraw. cur_pos:{}, reserve_pos:{}, diff:{}, price:{}",
      cur_pos, reserved_pos_, pos_diff, price
    );
    if (pos_diff > 0.0) {
      bool order_result = executor_->SubmitAggOrders(OrderSide::BUY_ORDER, price * 1.01, std::abs(pos_diff));
      if (order_result) {
        LOG(INFO) << "Order Submitted[BUY]!!";
      } else {
        LOG(INFO) << "Order Failed[BUY]!!";
      }
    } else {
      bool order_result = executor_->SubmitAggOrders(OrderSide::SELL_ORDER, price * 0.99, std::abs(pos_diff));
      if (order_result) {
        LOG(INFO) << "Order Submitted[BUY]!!";
      } else {
        LOG(INFO) << "Order Failed[BUY]!!";
      }
    }
    is_returning_pos_ = false;
  }
}

void SimpleAmmSubStrategy::InitalizePosition(double cur_price) {
  bool has_nft_token = total_nft_token_cnt_ > 0;
  if (has_nft_token && !pos_initialized_ && !is_pos_updating_) {
    is_pos_updating_ = true;
    LOG(INFO) << "Has position when strategy starts, updating.";
    LiquidityStatus liquidity_status = ReadStatusFromFile(cur_price);
    pricer_->onProvideSucceeded(liquidity_status.cur_price.value(), liquidity_status.price_upper.value(),
                                  liquidity_status.price_lower.value(), liquidity_status.amount.value(), liquidity_status.cur_fee.value());
    is_pos_updating_ = false;
  }
  pos_initialized_ = true;
  return;
}

void SimpleAmmSubStrategy::UpdatePosition() {
  bool has_nft_token = total_nft_token_cnt_ > 0;
  bool has_lp_pos = pricer_->HasLpPosition();
  if (has_lp_pos != has_nft_token && !is_pos_updating_) {
    is_pos_updating_ = true;
    if (has_lp_pos && op_withdraw_price_ > 0.0001) {
      pricer_->onWithdrawSucceeded(op_withdraw_price_);
      WriteStatusToFile(LiquidityStatus(std::nullopt, std::nullopt, std::nullopt, std::nullopt, std::nullopt));
      if (return_pos_after_withdraw_) {
        ReturnPosAfterWithdraw(op_withdraw_price_);
      }
      op_withdraw_price_ = 0.0;
    } else if (!has_lp_pos && op_provide_price_ > 0.0001) {
      pricer_->onProvideSucceeded(op_provide_price_, op_provide_price_upper_, op_provide_price_lower_, op_provide_amount_);
      WriteStatusToFile(LiquidityStatus(op_provide_price_, op_provide_price_upper_, op_provide_price_lower_, op_provide_amount_, 0.0));
      op_provide_price_ = 0.0;
      op_provide_price_lower_ = 0.0;
      op_provide_price_upper_ = 0.0;
      op_provide_amount_ = 0.0;
    } else {
      LOG(INFO) << "abnormal LP position change!!";
      pricer_->AlterLpPositionExplicit(has_nft_token);
    }
    is_pos_updating_ = false;
  }
}

void SimpleAmmSubStrategy::onBookFeed(const std::string& product_key, const FeedUpdate& upd) {
  CHECK(feed_keys_.find(product_key) != feed_keys_.end() or ref_feed_keys_.find(product_key) != ref_feed_keys_.end());
  if (HasMidPrice(upd)) {
    LOG(INFO) << fmt::format(
            "symbol:{}, ask0: {}, bid0: {}",
            product_key, upd.book().Ask0().value().price, upd.book().Bid0().value().price);
  }
  
  if (!(os()->is_ready() && GetMutableExecutor())) {
    return;
  }

  pricer_->onBookFeed(product_key, upd);

  if (feed_keys_.find(product_key) == feed_keys_.end()) {
    return;
  }

  trade_tick_cnt_++;
  if (!pos_initialized_) {
    InitalizePosition((upd.book().Ask0().value().price + upd.book().Bid0().value().price) / 2.0);
    return;
  }

  if (trade_tick_cnt_ % 5 == 0) {
    UpdatePosition();
  }

  if (upd.book().Timestamp() - last_op_ts_ < min_op_interval_) {
    LOG(INFO) << "in order cooldown.";
    return;
  }

  if (trade_tick_cnt_ % 1800 == 0 && pricer_->HasLpPosition()) {
    pricer_->PrintLpInfo();
    LiquidityStatus liquidity_status = pricer_->GetLiquidityStatus();
    WriteStatusToFile(liquidity_status);
  }

  auto lp_operation = pricer_->GetLiqidityOperation();
  if (lp_operation.liquidity_side == LiquiditySide::PROVIDE) {
    LOG(INFO) << fmt::format(
            "Order Sent[Provide liquidity]: {}, price_upper: {}, price_lower: {}",
            trade_product_->absolute_norm(), lp_operation.provide_price_lower, lp_operation.provide_price_upper);
    last_op_ts_ = upd.book().Timestamp();
    LiquiditySpec liquidity_spec{
      LiquiditySide::PROVIDE,
      trade_product_->absolute_norm(),
      lp_operation.provide_price_lower,
      lp_operation.provide_price_upper
    };
    os()->gateway()->SubmitLiquidity(liquidity_spec);
    LOG(INFO) << "Provide liquidity Submitted!!";
    double midp_price = (upd.book().Ask0().value().price + upd.book().Bid0().value().price) / 2.0;
    op_provide_price_ = midp_price;
    op_provide_price_upper_ = lp_operation.provide_price_upper;
    op_provide_price_lower_ = lp_operation.provide_price_lower;
    op_provide_amount_ = default_amount_;
    op_provide_amount_ = (executor_->GetPosition()) * midp_price * 2.0;
  } else if (lp_operation.liquidity_side == LiquiditySide::WITHDRAW) {
    LOG(INFO) << fmt::format(
            "Order Sent[Withdraw liquidity]: {}",
            trade_product_->absolute_norm());
    last_op_ts_ = upd.book().Timestamp();
    LiquiditySpec liquidity_spec{
      LiquiditySide::WITHDRAW,
      trade_product_->absolute_norm(),
      0.0,
      0.0
    };
    os()->gateway()->SubmitLiquidity(liquidity_spec);
    LOG(INFO) << "Withdraw liquidity Submitted!!";
    op_withdraw_price_ = (upd.book().Ask0().value().price + upd.book().Bid0().value().price) / 2.0;
  }

  if (os()->is_ready() && GetMutableExecutor()) {
    if (enable_telemetry_) {
      auto acc_request = GetAccRequest(upd.mea());
      auto feed_latency_tag = GetFeedLatencyTag(upd);
      coin2::strategy::util::SetFeedTimeToLatency(
          strat_logger_,
          feed_mgr_,
          upd,
          feed_latency_tag,
          acc_request,
          true);
      coin2::strategy::util::SetFeedTimeToLatency(
          strat_logger_,
          feed_mgr_,
          upd,
          coin::proto::LatencyProto::ORDER_LATENCY,
          acc_request,
          false);
    }
  }
}

}  // namespace coin2::strategy::dex_strat
